/*
** File: hey_edit.c
** Desc: screen handling functions for live editing
** Auth: Colm MacCarthaigh (colmmacc@redbrick.dcu.ie)
** Date: 5/9/01
*/

/*
** this is some pretty complicated shit - but well worth it :)
** 
** ANSI escapes rock!
**
** see the ansi file in doc/
**
** some things to note when changing stuff:
**
**    lines != rows
**    there may be rows above the cursor or below the cursor
**    from the _current_ line
**    
*/

#include <stdarg.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>

#include "hey_readline.h"
#include "hey_edit.h"
#include "hey_format.h"
#include "dyn_page.h"
#include "config.h"
#include "aux.h"

#include <readline/readline.h>
#include <readline/history.h>

/* the current buffer */
static dyn_page  * my_page;

/* do we even try to do anything */
static int hey_noaction = 0;

/* UTILITY FUNCTIONS */

/* save the state of the current line */
static void savestate(void) {

	if(hey_line == hey_linetotal && hey_lastdone == 0 ) {

		/* line is the last line, and hasnt been added */

		/* add it */
		dyn_pageAppend(my_page, rl_line_buffer);

		/* it's been done now */
		hey_lastdone = 1;

	} else {
	
		/* commit any changes */
		dyn_pageLineMod(my_page, rl_line_buffer, hey_line - 1);
	}

}

/* 
** returns the number of terminal rows the line takes up 
**
** if cursor is not -1 then it reports the number of rows:
**
**  direction > 0 : above cursor line
**  direction < 0 : below cursor line
**  
**  from the current line 
*/
static int rl_numrows(char * line, int cursor, int direction)
{
	int	total_len;
	int	ret;
	int	term_width;
	char	*envval;

	if(line == NULL)
		return strlen(hey_rlprompt);

	if((envval = getenv("COLUMNS")) == NULL)
		return 1;

	term_width = atoi(envval);	

	total_len = strlen(line) + strlen(hey_rlprompt);


	if(cursor != -1 && direction < 0) {
		/* get rid of any lines up until the line cursor is on */
		cursor += strlen(hey_rlprompt);
		for(; cursor > term_width; cursor -= term_width)
			total_len -= term_width;
	
		/* count the lines */
		for(ret = 0;  total_len > term_width; total_len -= term_width)
			ret++;
	
		return ret;
	}

	if(cursor != -1 && direction > 0) {
		/* pretend the line is only as long as the cursor pos */
		cursor += strlen(hey_rlprompt);

		for(ret = 0; cursor > term_width; cursor -= term_width)
			ret++;

		return ret;
	}

	for(ret = 1; total_len > term_width; total_len -= term_width)
		ret++;


	return ret;	
}

/* wrapper for the arrow keys */
int hey_arrows(int count, int key)
{
	int ch;

	ch = rl_read_key();

	switch(toupper(ch)) {
		case 'A':
			hey_lineup(count, ch);
			break;
		case 'B':
			hey_linedown(count, ch);
			break;
		case 'C':
			rl_forward(count, ch);
			break;
		case 'D':
			rl_backward(count, ch);
			break;
		default:
			rl_ding();
	}
	
	return 0;
}

int hey_bindkeys(void)
{

/* macro for binding a function in all keymaps */
#define RLBINDFUNC( seq , func )  \
	rl_generic_bind(ISFUNC , (seq) , (char *) (func), emacs_standard_keymap); \
	rl_generic_bind(ISFUNC , (seq) , (char *) (func), emacs_meta_keymap);     \
	rl_generic_bind(ISFUNC , (seq) , (char *) (func), emacs_ctlx_keymap);     \
	rl_generic_bind(ISFUNC , (seq) , (char *) (func), vi_movement_keymap);    \
	rl_generic_bind(ISFUNC , (seq) , (char *) (func), vi_insertion_keymap); 

/* macro to get rid of a funcion in all keymaps */
#define RLPURGEFUNC( func ) \
	rl_unbind_function_in_map( (func), emacs_standard_keymap); \
	rl_unbind_function_in_map( (func), emacs_meta_keymap); \
	rl_unbind_function_in_map( (func), emacs_ctlx_keymap); \
	rl_unbind_function_in_map( (func), vi_movement_keymap); \
	rl_unbind_function_in_map( (func), vi_insertion_keymap); 

	/* first get rid of anything that messes up formatting */

	rl_bind_key('\t', rl_insert);	 	/* get rid of completion */
	rl_bind_key( CTRL('l') , hey_refresh);  /* we have our own refresh */

	/* these can span lines */
	RLPURGEFUNC(rl_insert_comment);
	RLPURGEFUNC(rl_insert_comment);
	RLPURGEFUNC(rl_complete);
	RLPURGEFUNC(rl_insert_completions);
	RLPURGEFUNC(rl_possible_completions);
	RLPURGEFUNC(rl_menu_complete);
	RLPURGEFUNC(rl_reverse_search_history);
	RLPURGEFUNC(rl_forward_search_history);
	RLPURGEFUNC(rl_history_search_forward);
	RLPURGEFUNC(rl_history_search_backward);
	RLPURGEFUNC(rl_noninc_forward_search);
	RLPURGEFUNC(rl_noninc_reverse_search);
	RLPURGEFUNC(rl_noninc_forward_search_again);
	RLPURGEFUNC(rl_noninc_reverse_search_again);
	
	/* we have our own arrow keys and carriage return */
	rl_bind_key('\r', hey_return);
	rl_bind_key('\n', hey_return);

	RLBINDFUNC("\033[", hey_arrows);
	RLBINDFUNC("\033O", hey_arrows);
	RLBINDFUNC("\033k", hey_lineup);
	RLBINDFUNC("\033j", hey_linedown);

#undef RLBINDFUNC
#undef RLPURGEFUNC

	return 0;
}

/* is this TERM suitable */
static int is_term_ok(void)
{
	/* blank for now, but may become usefull */
	return 1;
}

/* END OF UTILITY FUNCTIONS */

/* move count lines up */
/* if key   < 0  - dont savestate */
/* if key == -2  - rl_point = 0 */
int hey_lineup(int count, int key)
{
	int cur_pos = 0;
	int cur_cache = 0;
	int cur_jump = 0;
	int i;

	if(hey_noaction)
		return 0;

	if((hey_line - count) <= 0) {
		rl_ding();
		return 0;
	}

	/* save the state of the current line */
	cur_pos = rl_point;
	if(key >= 0)
		savestate();	

	if(key == -2) {
		cur_cache = cur_pos;
		cur_pos = 0;
	}

	/* count how many rows <count> lines above us take up */
	for(i = (hey_line - abs(count)); i < hey_line; i++)
		cur_jump += rl_numrows( dyn_pageLineAt(my_page, i - 1) , -1, 0);

	/* add any extra rows produced by the current line */
	cur_jump += rl_numrows( dyn_pageLineAt(my_page, hey_line - 1), cur_pos, 1);

	/* 
	** move up count lines
	** move backwards 
	** kill the current line
	*/
	if(cur_jump)
		printf("\033[%dA", cur_jump);

	printf("\033[%dD\033[K", (int)strlen(hey_rlprompt) + cur_pos);

	fflush(stdout);

	/* change hey_line */
	hey_line -= count;

	/* insert the text */	
	rl_point = 0;
	rl_insert_text( dyn_pageLineAt(my_page, hey_line - 1));
	rl_delete_text( strlen(dyn_pageLineAt(my_page, hey_line - 1)), rl_end);

	/* fix the cursors position */
	if(rl_end < cur_pos) {
		rl_point = rl_end;
	} else {
		rl_point = cur_pos;
	}

	/* redisplay everything */
	if(key == -2)
		rl_point = cur_cache;
	rl_forced_update_display();

	return 1;
}

/* move count lines down */
int hey_linedown(int count, int key)
{
	int cur_pos = 0;
	int cur_jump = 0;
	int i;

	if(hey_noaction)
		return 0;

	if( (hey_line + count) > hey_linetotal ) {
		rl_ding(); return 0;
	}

	/* save the state of the current line */
	cur_pos = rl_point;
	savestate();	

	/* count how many rows <count> lines below us take up */
	for(i = hey_line + 1;  i < (hey_line + abs(count)); i++)
		cur_jump += rl_numrows( dyn_pageLineAt(my_page, i - 1) , -1, 0);

	cur_jump++;

	/* add any extra rows produced by the current line */
	cur_jump += rl_numrows( dyn_pageLineAt(my_page, hey_line - 1), rl_point, -1);

	/* 
	** move down count lines 
	** move backwards 
	** kill the current line
	*/
	if(cur_jump)
		printf("\033[%dB\033[%dD\033[K", cur_jump , (int)strlen(hey_rlprompt) + cur_pos);

	fflush(stdout);

	/* change hey_line */
	hey_line += count;

	/* insert the text */	
	rl_point = 0;
	rl_insert_text( dyn_pageLineAt(my_page, hey_line - 1) );
	rl_delete_text( strlen(dyn_pageLineAt(my_page, hey_line - 1)), rl_end);

	/* fix the cursors position */
	if(rl_end < cur_pos) {
		rl_point = rl_end;
	} else {
		rl_point = cur_pos;
	}

	/* redisplay everything */
	rl_forced_update_display();

	return 1;
}



/* for when we hit the return key */
int hey_return(int count, int key)
{
	

	/* test for EOF */
	if(hey_eof != NULL) {
		if(!strcmp(rl_line_buffer,hey_eof)) {
			rl_done = 1;
			return 1;
		}
	}

	/* are we on the last line ? */
	if (hey_line != hey_linetotal) {
		/* no */

		/* move down a line */
		return hey_linedown(1,0);

	} else {

		/* yes */
		savestate();

		rl_done = 1;

		/* skip any rows below us from the current line */		
		if( rl_numrows( rl_line_buffer , rl_point , -1) ) {
			printf("\033[%dC", rl_numrows( rl_line_buffer , rl_point , -1) );
		}

		/* print the newline */
		printf("\n");
		fflush(stdout);

		return 1;

	}

}

/* refrsh the screen */
int hey_refresh(int count, int key)
{
	int	i;

	if(hey_noaction)
		return 0;

	/* save the state of the current line */
	savestate();

	/* go to the start the current line */
	printf("\033[%dD", rl_point + (int)strlen(hey_rlprompt) );
	fflush(stdout);

	/* move up if desired */
	if(count) {
		/* move up the right number of lines */
		for(i=0; i < hey_line - 1; i++) {
			printf("\033[%dA", rl_numrows(dyn_pageLineAt(my_page, i), 0, 0));
			fflush(stdout);
		}
	
		if( rl_numrows(rl_line_buffer , rl_point, 1) ){
			printf("\033[%dA", rl_numrows(rl_line_buffer, rl_point, 1));
			fflush(stdout);
		}
	}

	/* print all the lines */
	for(i=0; i < hey_linetotal; i++) {
		printf("\033[K%s%s", hey_rlprompt,dyn_pageLineAt(my_page,i));
	
		if( i < (hey_linetotal -1))
			printf("\n");

		fflush(stdout);
	}

	/* go to the start the current line */
	printf("\033[%dD", (int)strlen( dyn_pageLineAt(my_page, i - 1 ) ) + (int)strlen(hey_rlprompt) );
	fflush(stdout);

	i=hey_line;
	hey_line=hey_linetotal;	
	if(hey_line != i ) {
		hey_lineup(hey_linetotal - i, -2);
	} else {
		hey_lineup(hey_linetotal - i, -1);
	}

	return 1;
}

/* Read user input into hey_page, also allocates it & suchlike */ 
void hey_rl_input(void) {
	char *line;
	char *ptr;
	int  i;

	/* Allocate hey_page, passing hey_wrap as the word wrapping width */
	if (!(my_page = dyn_pageAlloc(0)))
		printerr_exit ("Woah. Error allocating my_page.\n");


	/* we are editing now */
	hey_readline_init();

	if(is_term_ok() == 0)
		hey_noaction = 1;

	/* dont grab the lines, savestate will do that */
	while (hey_readline()); 

	/* done */
	hey_readline_fini();

	/* Allocate hey_page, passing hey_wrap as the word wrapping width */
	if (!(hey_page = dyn_pageAlloc(hey_wrap))) 
		printerr_exit ("Woah. Error allocating hey_page.\n");

	
	/* generate hey_page from my_page */
	if(!(line = dyn_pageRead(my_page)))
		printerr_exit("hey: no input or input lost. (preformat page)\n"
				"\nIf you did input something "
				"and this happened, please make a\n"
				"copy of your data and send it to "
				"c-hey@redbrick.dcu.ie for debugging.\n"
				"Thanks.\n\n");
	
	i = 0;
	do {
		i++;

		/* weed out empty last lines */
		if( ! (i == hey_linetotal && !strcmp(line,"")) ) {

			/* Clean up whitespace */
			for (ptr = line; *ptr; ptr++) {
				switch (*ptr) {
					case '\t':
					case '\f':
					case '\r':
					case '\n':
						*ptr = ' ';
				}	
			}

			/* add the line */
			dyn_pageAppend(hey_page, line);
		}

		
	} while ((line = dyn_pageRead(NULL)));

	dyn_pageFree(my_page);	

	/* And that's that ... */
	return;

}


